﻿using System;
using FlipRunner.Physics;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
using XnaPlus.Common;

namespace FlipRunner.Components
{
    public enum RunnerState
    {
        OnPlatform,

        InAir,

        OnGround,
    }

    public class Runner : DrawableGamePlusComponent, IPhysicsComponent
    {
        public const float Width = 0.4f;
        public const float Height = 2f;
        public const int MaxJumps = 2;

        private const float HorizontalInputAcceleration = 12f;

        private const float AirControlAccelerationFactor = 0.25f;

        private const float MinHorizontalVelocityMagnitude = 0.3f;

        /// <summary>
        /// Horizontal velocity magnitude loss per second
        /// </summary>
        private const float HorizontalBrakes = 5.7f;

        public const float DefaultJumpImpulse = 8;

        private int m_remainingJumps;

        /// <summary>
        /// Meters per second
        /// </summary>
        public const float MaxXAcceleration = 4;

        private RunnerState m_state;


        public Runner(GamePlus game)
            : base(game)
        {
            JumpImpulse = new CartesianVector(0, DefaultJumpImpulse);
            Velocity = CartesianVector.Zero;
        }

        public RunnerState State
        {
            get { return m_state; }
            set
            {
                if (m_state != value)
                {
                    RunnerState old = m_state;
                    m_state = value;
                    OnStateChanged(old, value);
                }
            }
        }


        public CartesianVector Velocity { get; set; }

        private static CartesianVector JumpImpulse { get; set; }

        private CartesianVector Acceleration
        {
            get
            {
                var accel = CartesianVector.Zero;
                //input acceleration
                if (Game.Input.IsKeyDown(Keys.Left))
                {
                    accel -= State == RunnerState.InAir
                                 ? AirControlAccelerationFactor*HorizontalInputAcceleration*CartesianVector.UnitX
                                 : HorizontalInputAcceleration*CartesianVector.UnitX;
                }

                if (Game.Input.IsKeyDown(Keys.Right))
                {
                    accel += State == RunnerState.InAir
                                 ? AirControlAccelerationFactor * HorizontalInputAcceleration * CartesianVector.UnitX
                                 : HorizontalInputAcceleration * CartesianVector.UnitX;
                }
                //gravity
                if (State == RunnerState.InAir)
                {
                    accel += Constants.Gravity;
                }

                //platform drag
                CartesianVector groundRelativeVelocity = Velocity - Game.PlatformVelocity;
                if (State == RunnerState.OnPlatform && Math.Abs(groundRelativeVelocity.X) > MinHorizontalVelocityMagnitude)
                {
                    accel -= HorizontalBrakes * CartesianVector.UnitX * Math.Sign(groundRelativeVelocity.X);
                }

                return accel;
            }
        }

        private float GroundOffset
        {
            get { return this.Bottom() - Game.Generator.GetMaxPlatformHeight(this.Left(), this.Right()); }
        }

        #region IPhysicsComponent Members

        public new FlipRunnerGame Game
        {
            get { return base.Game as FlipRunnerGame; }
        }

        public ISize Size
        {
            get { return new CartesianVector(Width, Height); }
        }

        public CartesianPoint Position { get; set; }

        public PositionAnchor Anchor
        {
            get { return PositionAnchor.Center; }
        }

        #endregion

        protected virtual void OnStateChanged(RunnerState previous, RunnerState current)
        {
            if (previous == RunnerState.InAir)
            {
                m_remainingJumps = MaxJumps;
            }
        }

        public override void Update(GameTime gameTime)
        {
            base.Update(gameTime);

            UpdateState();

            var elapsedSeconds = (float)gameTime.ElapsedGameTime.TotalSeconds;

            UpdateVelocity(elapsedSeconds);

            UpdatePosition(elapsedSeconds);

            #region Prevent Leaving World

            CartesianRectangle hitbox = this.GetCartesianHitBox();
            ISize worldSize = Game.WorldSize;

            if (hitbox.Left <= 0)
            {
                Position = new CartesianPoint(Size.Width / 2, Position.Y);
                Velocity = new CartesianVector(Math.Max(0, Velocity.X), Velocity.Y);
            }
            else if (hitbox.Right >= worldSize.Width)
            {
                Position = new CartesianPoint(Game.WorldSize.Width - Size.Width / 2, Position.Y);
                Velocity = new CartesianVector(Math.Min(0, Velocity.X), Velocity.Y);
            }

            float floorOffset = GroundOffset;
            if (floorOffset <= 0)
            {
                Position -= new CartesianVector(0, floorOffset);
                Velocity = new CartesianVector(Velocity.X, Math.Max(0, Velocity.Y));
            }
            else if (hitbox.Top >= worldSize.Height)
            {
                Position = new CartesianPoint(Position.X, Game.WorldSize.Height - Size.Height / 2);
                Velocity = new CartesianVector(Velocity.X, Math.Min(0, Velocity.Y));
            }

            #endregion
        }

        private void UpdateState()
        {
            if (GroundOffset <= 0)
            {
                State = this.Bottom() <= 0 ? RunnerState.OnGround : RunnerState.OnPlatform;
            }
            else
            {
                State = RunnerState.InAir;
            }
        }

        private void TryJump()
        {
            if (m_remainingJumps > 0)
            {
                m_remainingJumps--;
                Velocity += JumpImpulse;
            }
        }

        private void UpdateVelocity(float elapsedSeconds)
        {
            if (Game.Input.IsNewKeyPress(Keys.Space) || Game.Input.IsNewKeyPress(Keys.Up))
            {
                TryJump();
            }

            var accel = Acceleration;

            //velocity time step
            Velocity += elapsedSeconds * accel;

            if (accel.X == 0 && Math.Abs(Velocity.X - Game.PlatformVelocity.X) <= MinHorizontalVelocityMagnitude)
            {
                Velocity = new CartesianVector(Game.PlatformVelocity.X, Velocity.Y);
            }
        }

        private void UpdatePosition(float elapsedSeconds)
        {
            //position time step
            Position += Velocity * elapsedSeconds;

        }

        public override void Draw(GameTime gameTime)
        {
            base.Draw(gameTime);
            Rectangle drawRect = this.GetDrawRectangle();
            Game.Drawer.DrawRectangle(drawRect, Color.Red);
        }
    }
}